﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ComputerArchitecture.Core;
using ComputerArchitecture.Core.Logging;
using ComputerArchitecture.Numbers;
using ComputerArchitecture.Units.Interfaces;

namespace ComputerArchitecture.Units.Complement
{
    public class ComplementDivideUnit : LoggingObject, IDivideUnit
    {
        #region Fields

        private readonly ISubstractUnit _substractUnit;
        private readonly IAdderUnit _adderUnit;
        private readonly INegateUnit _negateUnit;

        #endregion

        #region Constructors

        public ComplementDivideUnit(ILoggingService loggingService, IAdderUnit adderUnit, ISubstractUnit substractUnit, INegateUnit negateUnit)
            : base(loggingService)
        {
            ArgumentValidator.NullValidate(adderUnit);
            _adderUnit = adderUnit;
            ArgumentValidator.NullValidate(substractUnit);
            _substractUnit = substractUnit;
            ArgumentValidator.NullValidate(negateUnit);
            _negateUnit = negateUnit;
        }

        #endregion

        public ISignedBitNumber Divide(ISignedBitNumber dividendMoreSignificant, ISignedBitNumber dividendLessSignificant, ISignedBitNumber divisor, out ISignedBitNumber remainder)
        {
            ArgumentValidator.NullValidate(dividendMoreSignificant);
            ArgumentValidator.NullValidate(dividendLessSignificant);
            ArgumentValidator.NullValidate(divisor);
            // check if divisor equals null
            ISignedBitNumber zero = ComplementBitNumber.GetZero(divisor.Size);
            if (divisor.Equals(zero))
            {
                throw new DivideByZeroException();
            }
            // todo: size checking;
            // make local copy of dividend's registers
            ISignedBitNumber dividendMoreSignificantLocal = (ISignedBitNumber)dividendMoreSignificant.Clone(),
                       dividendLessSignificantLocal = (ISignedBitNumber)dividendLessSignificant.Clone();

            for (int i = 0; i < divisor.Size; ++i)
            {
                this.LoggingService.LogInfo("Iteration {0}", i + 1);
                // shift left a and q
                {
                    Bit lessSignificantCarryBit, moreSignificantCarryBit;
                    this.LoggingService.LogInfo("Shift left dividend less significant bits");
                    dividendLessSignificantLocal.LogicShiftLeft(1, out lessSignificantCarryBit);
                    this.LoggingService.LogInfo("result: {0:d4}", dividendLessSignificantLocal);
                    // todo: check if we need to assign the least significant bit as lessSignificantCarryBit
                    this.LoggingService.LogInfo("Shift left dividend more significant bits and set the least significant bit as carry bit from less significant bits shift");
                    dividendMoreSignificantLocal.LogicShiftLeft(1, out moreSignificantCarryBit);
                    dividendMoreSignificantLocal[dividendMoreSignificantLocal.Size - 1].SetAs(lessSignificantCarryBit);
                    this.LoggingService.LogInfo("result: {0:d4}", dividendMoreSignificantLocal);
                }
                // if sign of more significant bits of dividend is equals divisor sign
                //     a = a - m
                // else
                //     a = a + m
                bool overflow = false;
                int beforeOperationMoreSignificantBitsSign = dividendMoreSignificantLocal.Sign;
                ISignedBitNumber temp = (ISignedBitNumber)dividendMoreSignificantLocal.Clone();
                if (dividendMoreSignificantLocal.Sign == divisor.Sign)
                {
                    this.LoggingService.LogInfo("More significant's sign equals divisor's sign - substract");
                    dividendMoreSignificantLocal = _substractUnit.Substract(dividendMoreSignificantLocal, divisor,
                                                                            out overflow);
                }
                else
                {
                    this.LoggingService.LogInfo("More significant's sign does't equal divisor's sign - add");
                    dividendMoreSignificantLocal = _adderUnit.Add(dividendMoreSignificantLocal, divisor, out overflow);
                }
                this.LoggingService.LogInfo("dividend more significant bits: {0:d4}", dividendMoreSignificantLocal);
                // check successful or unsuccessful
                // successful = a-sign is not changed
                if (dividendMoreSignificantLocal.Sign == beforeOperationMoreSignificantBitsSign | (dividendMoreSignificantLocal.Equals(zero) & dividendLessSignificantLocal.Equals(zero)))
                {
                    // success
                    // set Q_0
                    this.LoggingService.LogInfo("Dividend more significant bits' sign hasn't changed: success, set bit in less significant");
                    dividendLessSignificantLocal[dividendLessSignificantLocal.Size - 1].Set();
                }
                else
                {
                    this.LoggingService.LogInfo("Dividend more significant bits' sign changed: unsuccessful, restore dividend more significant and reset bit in less significant bits");
                    // reset q_0
                    dividendLessSignificantLocal[dividendLessSignificantLocal.Size - 1].Reset();
                    // restore a
                    dividendMoreSignificantLocal = temp;
                }
                this.LoggingService.LogInfo("Dividend less significant bits: {0:d4}\n", dividendLessSignificantLocal);
            }

            this.LoggingService.LogInfo("Remainder in more significant dividend's bits");
            remainder = dividendMoreSignificantLocal;
            this.LoggingService.LogInfo("Remainder: {0:d4}", remainder);
            this.LoggingService.LogInfo("Result in less significant dividend's bits");
            if (dividendMoreSignificant.Sign == divisor.Sign)
            {
                this.LoggingService.LogInfo("divident more significant sign equals divisor's sign: return");
                return dividendLessSignificantLocal;
            }
            this.LoggingService.LogInfo("divident more significant sign doesn't equal divisor's: negate result ");
            bool hasError = false;
            NegateSpecialCase specialCase = NegateSpecialCase.None;
            ISignedBitNumber result = _negateUnit.Negate(dividendLessSignificantLocal, out specialCase, out hasError);
            return specialCase == NegateSpecialCase.MaxNegativeValue ? dividendLessSignificantLocal : result;
        }

        public ISignedBitNumber Divide(ISignedBitNumber dividend, ISignedBitNumber divisor, out ISignedBitNumber remainder)
        {
            ISignedBitNumber dividendMoreSignificant = new ComplementBitNumber(divisor.Size), dividendLessSignificant = new ComplementBitNumber(divisor.Size);
            dividend.CopyTo(dividendMoreSignificant, 0, 0, dividendMoreSignificant.Size);
            dividend.CopyTo(dividendLessSignificant, 0, dividendMoreSignificant.Size, dividendLessSignificant.Size);
            return this.Divide(dividendMoreSignificant, dividendLessSignificant, divisor, out remainder);
        }
    }
}
